( function ( D ) {

    D.Elem = function ( elem, id ) 
    {
        var x = Math.random( ) * 700 + 50;
        var y = Math.random( ) * 500 + 50;
        var dx = 0, dy = 0;
        var outgoingLinks = [ ], incomingLinks = [ ];

        elem.attr( "x", x );
        elem.attr( "y", y );

        if( elem.type === "image" )
        {
            elem.node.ondblclick = function( )
            {
                var pageId;

                if( id.match( /^.*collections\/\d{4}$/ ) )
                {
                    pageId = id + "/0001";
                }
                else
                {
                    pageId = id;
                }
                self.opener.DESANTI.openPage( pageId ); 
                self.opener.focus( );
            }
        }

        var obj = 
        {
          outgoingLinks: function( ) 
          {
            return outgoingLinks;
          },

          incomingLinks: function( ) 
          {
            return incomingLinks;
          },

          x: function ( ) 
          {
            return x;
          },

          y: function ( ) 
          {
            return y;
          },

          cx: function ( ) 
          {
            return x + elem.getBBox( ).width / 2;
          },

          cy: function ( ) 
          {
            return y + elem.getBBox( ).height / 2;
          },

          moveTo: function( nx, ny ) 
          {
            x = nx;
            y = ny;
            elem.attr( "x", x );
            elem.attr( "y", y );

            for( var l = 0 ; l < outgoingLinks.length ; l++ ) 
            {
              outgoingLinks[ l ].line[ 0 ].pathSegList.getItem( 0 ).x = this.cx( );
              outgoingLinks[ l ].line[ 0 ].pathSegList.getItem( 0 ).y = this.cy( );
            }
            for ( var l = 0; l < incomingLinks.length; l++ ) 
            {
              incomingLinks[ l ].line[ 0 ].pathSegList.getItem( 1 ).x = this.cx( );
              incomingLinks[ l ].line[ 0 ].pathSegList.getItem( 1 ).y = this.cy( );
            }
          },

          friction: function( ) 
          {
            dx *= 0.99;
            dy *= 0.99;
          },

          linkTo: function( others, predicate ) 
          {
            var idRelation = id + predicate,
                relation; 

            if( !( D.elements[ idRelation ] ) ) 
            {
                relation = new D.Elem( D.r.text( 0, 0, decodeURIComponent( predicate ) ).attr( {
                                        "stroke": "#aaa", 
                                        "font-size": 20, 
                                        "fill": "#aaa"
                                     } ), idRelation );
                D.elements[ idRelation ] = relation;
            } 
            else 
            {
                relation = D.elements[ idRelation ];
            }

            for( var i = 0 ; i < others.length ; i+=1 ) 
            {
                var other = others[ i ];

                var line1 = D.r.path( "M" + this.cx( ) + " " + this.cy( ) + "L" + 
                                    relation.cx( ) + " " + relation.cy( ) ).attr( 
                            {
                                "stroke": "#aaa",
                                "stroke-width": 3
                            } );

                var line2 = D.r.path( "M" + relation.cx( ) + " " + relation.cy( ) + 
                                    "L" + other.cx( ) + " " + other.cy( ) ).attr(
                            {
                                "stroke": "#aaa",
                                "stroke-dasharray": "-",
                                "stroke-width": 3
                            } );

                line1[ 0 ].parentNode.insertBefore( line1[ 0 ], line1[ 0 ].parentNode.firstChild ); // move to back
                line2[ 0 ].parentNode.insertBefore( line2[ 0 ], line2[ 0 ].parentNode.firstChild ); // move to back

                var link1 = 
                {
                  start: this,
                  end: relation,
                  line: line1
                };

                var link2 = 
                {
                  start: relation,
                  end: other,
                  line: line2
                };

                outgoingLinks.push( link1 );
                relation.outgoingLinks( ).push( link2 );
                relation.incomingLinks( ).push( link1 );
                other.incomingLinks( ).push( link2 );
                elem[ 0 ].parentNode.appendChild( elem[ 0 ] );
            }
          },

          update: function ( ) 
          {
            var ax = 0, ay = 0;

            if( !( id === D.center ) ) 
            {
                for( var i in D.elements ) 
                {
                  if( D.elements[ i ] === this ) 
                  {
                      continue;
                  }

                  var f = D.repulse( x, y, D.elements[ i ].x( ), D.elements[ i ].y( ) );

                  ax += f.x * 10;
                  ay += f.y * 10;
                }

                for( var l = 0 ; l < outgoingLinks.length ; l++ ) 
                {
                  var other = outgoingLinks[ l ].end;
                  var f = D.attract( x, y, other.x( ), other.y( ) );
                  ax += f.x;
                  ay += f.y;
                }

                for( var l = 0 ; l < incomingLinks.length ; l++ ) 
                {
                  var other = incomingLinks[ l ].start;
                  var f = D.attract( x, y, other.x( ), other.y( ) );

                  ax += f.x;
                  ay += f.y;
                }

                dx += ax;
                dy += ay;

                if (dx > 1) dx = 1;
                if (dx < -1) dx = -1;
                if (dy > 1) dy = 1;
                if (dy < -1) dy = -1;

                if( ( x + dx > 0 ) && ( x + dx < D.holderWidth  ) &&
                    ( y + dy > 0 ) && ( y + dy < D.holderHeight ) &&
                    ( x + elem.getBBox().width + dx > 0 ) && ( x + elem.getBBox().width + dx < D.holderWidth ) &&
                    ( y + elem.getBBox().height + dy > 0 ) && ( y + elem.getBBox().height + dy < D.holderHeight ) )
                {
                    this.moveTo( x + dx, y + dy );
                    this.friction( );
                }
            } 
            else 
            {
                this.moveTo( ( D.holderWidth / 2 )  - elem.getBBox().width  / 2, 
                             ( D.holderHeight / 2 ) - elem.getBBox().height / 2 );
            }
          }
        };

        elem.node.onmousedown = function( e ) 
        {
          D.dragObj = obj;
          D.dragStart = { x: e.clientX, y: e.clientY };
          D.dragObjStart = { x: D.dragObj.x( ), y: D.dragObj.y( ) };
          this.parentNode.appendChild( this ); // bring to front

          return false;
        };

        return obj;
    }

} ) ( DESANTI );
